#include "MexicanHatFunction.h"
#include <math.h>

using namespace NeuralPlusPlus::Core;
using namespace NeuralPlusPlus::Core::SOM;

NeuralPlusPlus::Core::SOM::NeighborhoodFunctions::MexicanHatFunction::MexicanHatFunction( double learningRadius )
	{
	// Full Width at Half Maximum for a Mexican Hat curve 
	//        = 1.2518753 * sigma
	// Full Width at Half Maximum (FWHM) is nothing but learning diameter
	// so, learning radius = 0.62593765 * sigma

	this->Sigma = learningRadius / 0.6259;
	}

void NeuralPlusPlus::Core::SOM::NeighborhoodFunctions::MexicanHatFunction::EvaluateNeighborhood( KohonenLayer *layer, int currentIteration, int trainingEpochs )
	{
	Helper::ValidateNotNull(layer, "layer");
	Helper::ValidatePositive(trainingEpochs, "trainingEpochs");
	Helper::ValidateWithinRange(currentIteration, 0, trainingEpochs - 1, "currentIteration");

	// Winner co-ordinates
	int winnerX = layer->winner->Coordinate.X;
	int winnerY = layer->winner->Coordinate.Y;

	// Layer width and height
	int layerWidth = layer->size.Width;
	int layerHeight = layer->size.Height;

	// Optimization: Pre-calculated 2-Sigma-Square (1e-9 to make sure it is non-zero)
	double sigmaSquare = Sigma * Sigma + 1e-9;

	// Evaluate and update neighborhood value of each neuron
	int neuronsInLayer = layer->NeuronsLength;
	for(int i=0;i<neuronsInLayer;i++)
		{
		PositionNeuron *neuron = (PositionNeuron*)(*layer)[i];
		int dx = abs(winnerX - neuron->Coordinate.X);
		int dy = abs(winnerY - neuron->Coordinate.Y);

		if (layer->isRowCircular)
			{
			dx = min(dx, layerWidth - dx);
			}
		if (layer->isColumnCircular)
			{
			dy = min(dy, layerHeight - dy);
			}

		double dxSquare = dx * dx;
		double dySquare = dy * dy;
		if (layer->topology == LatticeTopologyType::Hexagonal)
			{
			if (dy % 2 == 1)
				{
				dxSquare += 0.25 + (((neuron->Coordinate.X > winnerX) == (winnerY % 2 == 0)) ? dx : -dx);
				}
			dySquare *= 0.75;
			}
		double distanceBySigmaSquare = (dxSquare + dySquare) / sigmaSquare;
		neuron->NeighborhoodValue = (1 - distanceBySigmaSquare) * exp(-distanceBySigmaSquare / 2);
		}
	}